package test.base.medium.ffmpeg;

import static org.bytedeco.ffmpeg.global.avcodec.av_packet_unref;
import static org.bytedeco.ffmpeg.global.avformat.AVFMT_NOFILE;
import static org.bytedeco.ffmpeg.global.avformat.AVIO_FLAG_WRITE;
import static org.bytedeco.ffmpeg.global.avformat.av_dump_format;
import static org.bytedeco.ffmpeg.global.avformat.av_guess_format;
import static org.bytedeco.ffmpeg.global.avformat.av_interleaved_write_frame;
import static org.bytedeco.ffmpeg.global.avformat.av_write_frame;
import static org.bytedeco.ffmpeg.global.avformat.av_write_trailer;
import static org.bytedeco.ffmpeg.global.avformat.avformat_alloc_output_context2;
import static org.bytedeco.ffmpeg.global.avformat.avformat_write_header;
import static org.bytedeco.ffmpeg.global.avformat.avio_alloc_context;
import static org.bytedeco.ffmpeg.global.avformat.avio_close;
import static org.bytedeco.ffmpeg.global.avformat.avio_open2;
import static org.bytedeco.ffmpeg.global.avutil.AVMEDIA_TYPE_AUDIO;
import static org.bytedeco.ffmpeg.global.avutil.AVMEDIA_TYPE_VIDEO;
import static org.bytedeco.ffmpeg.global.avutil.AV_LOG_INFO;
import static org.bytedeco.ffmpeg.global.avutil.AV_NOPTS_VALUE;
import static org.bytedeco.ffmpeg.global.avutil.AV_PIX_FMT_NONE;
import static org.bytedeco.ffmpeg.global.avutil.AV_ROUND_NEAR_INF;
import static org.bytedeco.ffmpeg.global.avutil.AV_ROUND_PASS_MINMAX;
import static org.bytedeco.ffmpeg.global.avutil.av_dict_free;
import static org.bytedeco.ffmpeg.global.avutil.av_dict_set;
import static org.bytedeco.ffmpeg.global.avutil.av_free;
import static org.bytedeco.ffmpeg.global.avutil.av_log_get_level;
import static org.bytedeco.ffmpeg.global.avutil.av_malloc;
import static org.bytedeco.ffmpeg.global.avutil.av_rescale_q;
import static org.bytedeco.ffmpeg.global.avutil.av_rescale_q_rnd;
import static org.bytedeco.ffmpeg.global.swresample.swr_free;
import static org.bytedeco.ffmpeg.global.swscale.sws_freeContext;

import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.Buffer;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.bytedeco.ffmpeg.avcodec.AVCodecContext;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.avformat.AVIOContext;
import org.bytedeco.ffmpeg.avformat.AVOutputFormat;
import org.bytedeco.ffmpeg.avformat.AVStream;
import org.bytedeco.ffmpeg.avformat.Seek_Pointer_long_int;
import org.bytedeco.ffmpeg.avformat.Write_packet_Pointer_BytePointer_int;
import org.bytedeco.ffmpeg.avutil.AVDictionary;
import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.javacpp.Pointer;
import org.bytedeco.javacv.Seekable;

import org.bytedeco.javacv.FrameRecorder.Exception;

import hyl.core.MyFun;

public class ACBase  implements Closeable {
	static Map<Pointer, OutputStream> outputStreams = Collections.synchronizedMap(new HashMap<Pointer, OutputStream>());
	// c语言的回调函数 调用了java 中的代码
	static SeekCallback 跳过回调 = new SeekCallback().retainReference();
	static WriteCallback 写回调 = new WriteCallback().retainReference();
	// 创建输出上下文
	AVFormatContext 输出设备 = null;
	AVIOContext avio;
	AVOutputFormat oformat = null;
	ACVideo 视频部分 = new ACVideo();
	ACAudio 音频部分 = new ACAudio();
	String _url;
	int ret = 0;
	protected Map<String, String> 公共配置项 = new HashMap<String, String>();
	protected Map<String, String> 公共元结构 = new HashMap<String, String>();
	OutputStream _跳转=null;
	boolean closeOutputStream=false;

	boolean 启动 = false;

	boolean 是否帧交错 = false;

	/**
	 * 
	 * 创建输出文
	 * 
	 * 通过输出文 分解出
	 * 
	 * @throws Exception
	 */
	public ACBase do1_创建输出设备(String url, String 格式名, OutputStream 跳转) throws Exception {
		_跳转=跳转;
		if (MyFun.isEmpty(url)) {
			return null;
		}
		_url = url;
		if (MyFun.isEmpty(格式名))
			格式名 = null;
		// 查找匹配的输出格式
	
		输出设备 = new AVFormatContext(null);
		if ((oformat = av_guess_format(格式名, url, null)) == null) {
			int proto = url.indexOf("://");
			if (proto > 0) {
				格式名 = url.substring(0, proto);
			}
			if ((oformat = av_guess_format(格式名, url, null)) == null) {
				throw new Exception("av_guess_format() 错误:找不到 \"" + url + "\" 的相近格式 " + 格式名);
			}
			格式名 = oformat.name().getString();
		}

		if (avformat_alloc_output_context2(输出设备, null, 格式名, url) < 0) {
			throw new Exception("avformat_alloc_context2() error:\t无法创建媒体上下文");
		}



		// 从特定的位置开始读取
		if (跳转 != null) {
			avio = avio_alloc_context(new BytePointer(av_malloc(4096)), 4096, 1, 输出设备, null, 写回调,
					跳转 instanceof Seekable ? 跳过回调 : null);
			输出设备.pb(avio);
			url = 跳转.toString();
			outputStreams.put(输出设备, 跳转);
		}
		输出设备.oformat(oformat);
		输出设备.url().putString(url);
		// 设置最大tcp延时等等
		输出设备.max_delay(-1);

		return this;
	}

	AVFormatContext 输入设备;

	public ACBase do2_连接输入设备(AVFormatContext 输入设备) throws Exception {
		// AVStream 输入视频流 = null, 输出视频流 = null;
		this.输入设备 = 输入设备;
		if (输出设备 != null) {
			// 从输入设备 分流出视频流和音频流
			for (int idx = 0; idx < 输入设备.nb_streams(); idx++) {
				AVStream inputStream = 输入设备.streams(idx);
				if (inputStream.codecpar().codec_type() == AVMEDIA_TYPE_VIDEO) {
					视频部分.do1_创建并配置(this, inputStream);
				} else if (inputStream.codecpar().codec_type() == AVMEDIA_TYPE_AUDIO) {
					// inpAudioStream = inputStream;
					// audioCodec = inpAudioStream.codec().codec_id();
					音频部分.do1_创建并配置(this, inputStream);
				}
			}
		}
		return this;
	}

	static class WriteCallback extends Write_packet_Pointer_BytePointer_int {
		@Override
		public int call(Pointer opaque, BytePointer buf, int buf_size) {
			try {
				byte[] b = new byte[buf_size];
				OutputStream os = outputStreams.get(opaque);
				buf.get(b, 0, buf_size);
				os.write(b, 0, buf_size);
				return buf_size;
			} catch (Throwable t) {
				System.err.println("OutputStream.write() 异常: " + t);
				return -1;
			}
		}
	}

	static class SeekCallback extends Seek_Pointer_long_int {

		@Override
		public long call(Pointer opaque, long offset, int whence) {
			try {
				OutputStream os = outputStreams.get(opaque);
				((Seekable) os).seek(offset, whence);
				return 0;
			} catch (Throwable t) {
				System.err.println(" OutputStream.seek() 异常: " + t);
				return -1;
			}
		}
	}


	public void do3_启动() throws Exception {
		// 公共选项
		AVDictionary dictopts = new AVDictionary(null);
		for (Entry<String, String> e : this.公共配置项.entrySet()) {
			av_dict_set(dictopts, e.getKey(), e.getValue(), 0);
		}
		if ((输出设备.flags() & AVFMT_NOFILE) == 0) {
			AVIOContext pb = new AVIOContext(null);
			if ((ret = avio_open2(pb, _url, AVIO_FLAG_WRITE, null, dictopts)) < 0) {
				free();
				av_dict_free(dictopts);
				throw new Exception("avio_open2 error() error " + ret + ": 不能打开'" + _url + "'");
			}
			输出设备.pb(pb);
		}
		// 公共元
		AVDictionary dictmeta = new AVDictionary(null);
		for (Entry<String, String> e : this.公共元结构.entrySet()) {
			av_dict_set(dictmeta, e.getKey(), e.getValue(), 0);
		}
		/* 写入文件头,任何时候都可以 */
		if ((ret = avformat_write_header(输出设备.metadata(dictmeta), dictopts)) < 0) {
			free();
			av_dict_free(dictopts);
			throw new Exception("avformat_write_header error() error " + ret + ": 不能写入头部");
		}
		av_dict_free(dictmeta);
		// 输出日志
		if (av_log_get_level() >= AV_LOG_INFO) {
			av_dump_format(输出设备, 0, _url, 1);
		}
		启动 = true;
	}


	public  void writePacket(int mediaType, AVPacket avPacket) throws Exception {
		boolean flag = (mediaType == AVMEDIA_TYPE_VIDEO || mediaType == AVMEDIA_TYPE_AUDIO) ? true : false;
		// AVStream avStream = (mediaType == AVMEDIA_TYPE_VIDEO) ? audio_st : (mediaType
		// == AVMEDIA_TYPE_AUDIO) ? video_st : null;
		String mediaTypeStr = (mediaType == AVMEDIA_TYPE_VIDEO) ? "视频"
				: (mediaType == AVMEDIA_TYPE_AUDIO) ? "音频" : "不支持的多媒体流格式";

		synchronized (输出设备) {
			int ret;
			/**
			 * 此函数将根据需要在内部缓冲数据包，以确保输出文件中的数据包按照以下顺序正确地交织在一起
			 * 
			 * 增加dts。做自己交错的呼叫者应该调用av_write_frame（）而不是此函数。
			 * 
			 * 使用此函数代替av_write_frame（）可以提高muxer的性能对未来数据包的了解，
			 * 
			 * 例如改进mp4的行为分节模式下VFR内容的muxer。
			 */
			if (是否帧交错 && flag) {
				if ((ret = av_interleaved_write_frame(输出设备, avPacket)) < 0) {
					throw new Exception("av_interleaved_write_frame() error " + ret + " 当写入 " + mediaTypeStr + " 包时");
				}
			} else {
				/*
				 * 此函数将数据包直接传递给muxer，不需要任何缓冲或者重新排序。 调用者负责正确地交错数据包，如果格式交错的话。
				 * 用libavformat.av_interleaved_write_frame（） 调用，
				 */
				if ((ret = av_write_frame(输出设备, avPacket)) < 0) {
					throw new Exception("av_write_frame() error " + ret + "  当写入" + mediaTypeStr + " 包时");
				}
			}
		}

		av_packet_unref(avPacket);
	}

	public boolean do4_recodePacket(AVPacket pkt) throws Exception {
		if (输入设备 == null) {
			throw new Exception("输入设备为空异常");
		}
		if (!启动) {
			throw new Exception("已经关闭");
		}

		if (pkt == null) {
			return false;
		}

		AVStream in_stream = 输入设备.streams(pkt.stream_index());
		/*
		 * 修复解码和回放错误造成的问题
		 * 
		 * 输出音频/视频文件或音频/视频流中的dts/pts，注释掉
		 * 
		 * 这一行代码使PTS/DTS可以手动指定时间戳。
		 */
//	        pkt.dts(AV_NOPTS_VALUE);
//	        pkt.pts(AV_NOPTS_VALUE);
		AVStream vds=视频部分.输出流;
		AVStream ads=音频部分.输出流;
		pkt.pos(-1);
		if (in_stream.codecpar().codec_type() == AVMEDIA_TYPE_VIDEO &&vds!= null) {

			pkt.stream_index(vds.index());
			pkt.duration(
					(int) av_rescale_q(pkt.duration(), in_stream.time_base(), vds.time_base()));
			pkt.pts(av_rescale_q_rnd(pkt.pts(), in_stream.time_base(), vds.time_base(),
					(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)));// Increase pts calculation
			pkt.dts(av_rescale_q_rnd(pkt.dts(), in_stream.time_base(), vds.time_base(),
					(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)));// Increase dts calculation
			writePacket(AVMEDIA_TYPE_VIDEO, pkt);

		} else if (in_stream.codecpar().codec_type() == AVMEDIA_TYPE_AUDIO && ads != null && (ads.codecpar().channels() > 0)) {

			pkt.stream_index(ads.index());
			pkt.duration(
					(int) av_rescale_q(pkt.duration(), in_stream.time_base(), ads.time_base()));
			pkt.pts(av_rescale_q_rnd(pkt.pts(), in_stream.time_base(), ads.time_base(),
					(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)));// Increase pts calculation
			pkt.dts(av_rescale_q_rnd(pkt.dts(), in_stream.time_base(), ads.time_base(),
					(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)));// Increase dts calculation
			writePacket(AVMEDIA_TYPE_AUDIO, pkt);
		}

		return true;
	}
	

    public void flush() throws Exception {
        synchronized (输出设备) {
            /*flush 剩余流字节 */
            while (视频部分.输出流 != null && 输入设备 == null && 视频部分.recordImage(0, 0, 0, 0, 0, AV_PIX_FMT_NONE, (Buffer[])null));
            while (音频部分.输出流 != null && 输入设备 == null && 音频部分.recordSamples(0, 0, (Buffer[])null));

            if (是否帧交错 && (视频部分.输出流 != null || 音频部分.输出流  != null)) {
                av_interleaved_write_frame(输出设备,null);
            } else {
                av_write_frame(输出设备, null);
            }

            /* 写入尾部 */
            av_write_trailer(输出设备);
        }
    }

    
public void free() {
	   启动 = false;
	   _url = null;

       AVFormatContext outputStreamKey = 输出设备;
       if (输出设备 != null && !输出设备.isNull()) {
           if (_跳转 == null && (oformat.flags() & AVFMT_NOFILE) == 0) {
               /* close the output file */
               avio_close(输出设备.pb());
           }

           /* free the streams */
           int nb_streams = 输出设备.nb_streams();
           for (int i = 0; i < nb_streams; i++) {
               av_free(输出设备.streams(i).codec());
               av_free(输出设备.streams(i));
           }

           /* free metadata */
           if (输出设备.metadata() != null) {
               av_dict_free(输出设备.metadata());
               输出设备.metadata(null);
           }

           /* free the stream */
           av_free(输出设备);
           输出设备 = null;
       }
//
//       if (img_convert_ctx != null) {
//           sws_freeContext(img_convert_ctx);
//           img_convert_ctx = null;
//       }
//
//       if (samples_convert_ctx != null) {
//           swr_free(samples_convert_ctx);
//           samples_convert_ctx = null;
//       }

       if (_跳转 != null) {
           try {
               if (closeOutputStream) {
            	   _跳转.close();
               }
           } catch (IOException ex) {
              ex.printStackTrace();
           } finally {
        	   _跳转 = null;
               outputStreams.remove(outputStreamKey);
               if (avio != null) {
                   if (avio.buffer() != null) {
                       av_free(avio.buffer());
                       avio.buffer(null);
                   }
                   av_free(avio);
                   avio = null;
               }
           }
       }
}
    @Override protected void finalize() throws Throwable {
        super.finalize();
        free();
    }
    public void close() throws Exception {
        if (输出设备 != null) {
            try {
                flush();
            } finally {
            	free();
            }
        }
    }
}
